package com.boot.security.service.authentication.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.boot.security.mapper.UserMapper;
import com.boot.security.model.bo.LoginAccountAuthentication;
import com.boot.security.model.bo.TokenAuthentication;
import com.boot.security.model.dto.LoginAccount;
import com.boot.security.model.dto.RegisterAccount;
import com.boot.security.model.po.Access;
import com.boot.security.model.po.User;
import com.boot.security.model.vo.AccountInfo;
import com.boot.security.model.vo.WebDetail;
import com.boot.security.service.authentication.AccountService;
import com.boot.security.service.database.UserAccessService;
import com.boot.web.config.ResourceProperties;
import com.boot.web.exception.GlobalException;
import com.boot.web.http.Status;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.io.File;
import java.io.IOException;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @date 2020/4/12 17:57
 **/
@Slf4j
@EnableConfigurationProperties(ResourceProperties.class)
@Service
public class AccountServiceImpl implements AccountService {

    private final UserMapper userMapper;

    private final UserAccessService userAccessService;

    private final PasswordEncoder passwordEncoder;

    private final ResourceProperties resourceProperties;
    private static final String AVATAR_PREFIX = "/openRoute/";

    private final Validator validator;

    public AccountServiceImpl(UserAccessService userAccessService, PasswordEncoder passwordEncoder, ResourceProperties resourceProperties, Validator validator) {
        this.userMapper = userAccessService.getMMapper();
        this.userAccessService = userAccessService;
        this.passwordEncoder = passwordEncoder;
        this.resourceProperties = resourceProperties;
        this.validator = validator;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean register(RegisterAccount registerAccount) {
        Set<ConstraintViolation<RegisterAccount>> violations = validator.validate(registerAccount, Default.class);
        if (violations.size() > 0) {
            throw new AuthenticationServiceException(violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList()).toString());
        }

        User registerUser = User.builder().username(registerAccount.getUsername()).build();
        User user = userMapper.selectOne(Wrappers.query(registerUser));
        if (user == null) {
            registerUser.setEmail(registerAccount.getEmail());
            registerUser.setPassword(passwordEncoder.encode(registerAccount.getPassword()));
            registerUser.setStatus(0);
            userMapper.insert(registerUser);
            return true;
        } else {
            throw new AuthenticationServiceException("存在相同用户");
        }
    }

    @Override
    public AccountInfo getAccountInformation() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication instanceof TokenAuthentication) {
            return ((TokenAuthentication) authentication).getAccountInfo();
        } else {
            throw new AuthenticationServiceException(String.format("Authentication 仅支持 %s",
                    TokenAuthentication.class.getSimpleName()));
        }
    }

    @Override
    public Authentication authenticate(LoginAccountAuthentication authentication) throws AuthenticationException {
        User authUser = new User();
        LoginAccount loginAccount = authentication.getLoginAccount();
        switch (loginAccount.getType()) {
            case LoginAccount.USERNAME_PASSWORD:
                authUser.setUsername(loginAccount.getPrincipal());
                break;
            case LoginAccount.PHONE_PASSWORD:
                authUser.setPhone(loginAccount.getPrincipal());
                break;
            case LoginAccount.EMAIL_PASSWORD:
                authUser.setEmail(loginAccount.getPrincipal());
                break;
            default:
                throw new AuthenticationServiceException("登录方式错误");
        }
        User user = userMapper.selectOne(Wrappers.query(authUser));
        if (user == null) {
            throw new UsernameNotFoundException("没有找到该用户");
        } else {
            if (user.getStatus() == 0) {
                // 账户正常
                if (passwordEncoder.matches(loginAccount.getCredential(), user.getPassword())) {
                    AccountInfo info = new AccountInfo();
                    WebDetail details = authentication.getDetails();
                    BeanUtils.copyProperties(details, info);
                    BeanUtils.copyProperties(user, info);

                    Set<Access> accesses = userAccessService.searchUserAllAccesses(user.getId());

                    //                    Set<Role> roles = userRoleService.searchUserAllRoles(user.getId());
                    //                    info.setRoles(roles.stream().map(Role::getCode).collect(Collectors.toSet()));
                    //                    Set<Access> accesses = roleAccessService.searchRolesAccesses(roles.stream()
                    //                            .map(Role::getId).collect(Collectors.toSet()));

                    info.setAccesses(accesses.stream()
                            .map(Access::getCode).collect(Collectors.toSet()));
                    TokenAuthentication tokenAuthentication = new TokenAuthentication(info);
                    tokenAuthentication.setAuthenticated(true);
                    return tokenAuthentication;
                } else {
                    throw new BadCredentialsException("密码错误");
                }
            } else {
                // 账户禁用
                throw new LockedException("账户禁用");
            }
        }
    }

    @Override
    public AccountInfo upgradeAccountInformation(AccountInfo oldInfo) {
        User user = userMapper.selectById(oldInfo.getId());
        if (user == null) {
            throw new UsernameNotFoundException("没有找到该用户");
        } else {
            AccountInfo info = new AccountInfo();
            BeanUtils.copyProperties(oldInfo, info);
            BeanUtils.copyProperties(user, info);

            //            Set<Role> roles = userRoleService.searchUserAllRoles(user.getId());
            //            info.setRoles(roles.stream().map(Role::getCode).collect(Collectors.toSet()));
            //            Set<Access> accesses = roleAccessService.searchRolesAccesses(roles.stream()
            //                    .map(Role::getId).collect(Collectors.toSet()));

            Set<String> accesses = userAccessService.searchUserAllAccesses(user.getId())
                    .stream().map(Access::getCode).collect(Collectors.toSet());

            info.setAccesses(accesses);
            return info;
        }
    }

    @Override
    public boolean upgradePassword(String oldPassword, String newPassword) {
        AccountInfo info = this.getAccountInformation();
        User user = userMapper.selectById(info.getId());
        if (user == null) {
            throw new GlobalException(Status.NO_USER, HttpStatus.BAD_REQUEST);
        } else {
            if (passwordEncoder.matches(oldPassword, user.getPassword())) {
                user.setPassword(passwordEncoder.encode(newPassword));
                return userMapper.updateById(user) > 0;
            } else {
                throw new GlobalException(Status.PASSWORD_ERROR, HttpStatus.BAD_REQUEST);
            }
        }
    }

    @Override
    public boolean upgradePhone(String password, String newPhone) {
        AccountInfo info = this.getAccountInformation();
        User user = userMapper.selectById(info.getId());
        if (user == null) {
            throw new GlobalException(Status.NO_USER, HttpStatus.BAD_REQUEST);
        } else {
            if (passwordEncoder.matches(password, user.getPassword())) {
                user.setPhone(newPhone);
                return userMapper.updateById(user) > 0;
            } else {
                throw new GlobalException(Status.PASSWORD_ERROR, HttpStatus.BAD_REQUEST);
            }
        }
    }

    @Override
    public boolean deletePhone() {
        AccountInfo info = this.getAccountInformation();
        User user = userMapper.selectById(info.getId());
        if (user == null) {
            throw new GlobalException(Status.NO_USER, HttpStatus.BAD_REQUEST);
        } else {
            user.setPhone(null);
            return userMapper.alwaysUpdateSomeColumnById(user) > 0;
        }
    }

    @Override
    public boolean upgradeEmail(String password, String newEmail) {
        AccountInfo info = this.getAccountInformation();
        User user = userMapper.selectById(info.getId());
        if (user == null) {
            throw new GlobalException(Status.NO_USER, HttpStatus.BAD_REQUEST);
        } else {
            if (passwordEncoder.matches(password, user.getPassword())) {
                user.setEmail(newEmail);
                return userMapper.updateById(user) > 0;
            } else {
                throw new GlobalException(Status.PASSWORD_ERROR, HttpStatus.BAD_REQUEST);
            }
        }
    }

    @Override
    public boolean deleteEmail() {
        AccountInfo info = this.getAccountInformation();
        User user = userMapper.selectById(info.getId());
        if (user == null) {
            throw new GlobalException(Status.NO_USER, HttpStatus.BAD_REQUEST);
        } else {
            user.setEmail(null);
            return userMapper.alwaysUpdateSomeColumnById(user) > 0;
        }
    }

    @Override
    public String upgradeAvatar(MultipartFile avatar) {
        if (avatar == null || avatar.isEmpty()) {
            return null;
        } else {
            String fileName = avatar.getOriginalFilename();
            assert fileName != null;
            String suffix = fileName.substring(fileName.lastIndexOf("."));

            AccountInfo info = this.getAccountInformation();

            String avatarName = info.getId() + "_avatar_" + IdUtil.fastSimpleUUID() + suffix;
            File dest = new File(resourceProperties.getSafeOpenRoute() + avatarName);
            try {
                avatar.transferTo(dest);

                User user = userMapper.selectById(info.getId());
                if (user == null) {
                    throw new GlobalException(Status.NO_USER, HttpStatus.BAD_REQUEST);
                } else {
                    String previous = user.getAvatar();
                    user.setAvatar(AVATAR_PREFIX + avatarName);
                    boolean result = userMapper.updateById(user) > 0;

                    if (StringUtils.hasText(previous) && previous.startsWith(AVATAR_PREFIX)) {
                        String previousAvatarRoute = resourceProperties.getSafeOpenRoute()
                                + previous.substring(AVATAR_PREFIX.length());
                        System.out.println(previousAvatarRoute);
                        File previousAvatar = new File(previousAvatarRoute);
                        if (previousAvatar.exists()) {
                            boolean deleteResult = previousAvatar.delete();
                            if (!deleteResult) {
                                log.warn("用户旧头像未被删除，路径: {}", previousAvatarRoute);
                            }
                        }
                    }
                    if (result) {
                        return AVATAR_PREFIX + avatarName;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

}
